<?php

/**
 * JPGraph v4.0.3
 */

namespace lemon\JpGraph\Graph;

use lemon\JpGraph\Util;

/**
 * File:		JPGRAPH_DATE.PHP
 * // Description: Classes to handle Date scaling
 * // Created:	 2005-05-02
 * // Ver:		 $Id: jpgraph_date.php 1106 2009-02-22 20:16:35Z ljp $
 * //
 * // Copyright (c) Asial Corporation. All rights reserved.
 */
class DateScale extends LinearScale
{
	private $date_format	 = '';
	private $iStartAlign	 = false;
	private $iEndAlign	   = false;
	private $iStartTimeAlign = false;
	private $iEndTimeAlign   = false;

	/**
	 * CONSTRUCTOR.
	 *
	 * @param mixed $aMin
	 * @param mixed $aMax
	 * @param mixed $aType
	 */
	public function __construct($aMin = 0, $aMax = 0, $aType = 'x'){
		assert($aType == 'x');
		assert($aMin <= $aMax);

		$this->type	   = $aType;
		$this->scale	  = [$aMin, $aMax];
		$this->world_size = $aMax - $aMin;
		$this->ticks	  = new LinearTicks();
		$this->intscale   = true;
	}

	/**
	 * Utility Function AdjDate()
	 * // Description: Will round a given time stamp to an even year, month or day
	 * // argument.
	 *
	 * @param mixed $aTime
	 * @param mixed $aRound
	 * @param mixed $aYearType
	 * @param mixed $aMonthType
	 * @param mixed $aDayType
	 */
	public function AdjDate($aTime, $aRound = 0, $aYearType = false, $aMonthType = false, $aDayType = false){
		$y = (int) date('Y', $aTime);
		$m = (int) date('m', $aTime);
		$d = (int) date('d', $aTime);
		$h = 0;
		$i = 0;
		$s = 0;
		if ($aYearType !== false) {
			$yearAdj = [0 => 1, 1 => 2, 2 => 5];
			if ($aRound == 0) {
				$y = floor($y / $yearAdj[$aYearType]) * $yearAdj[$aYearType];
			} else {
				++$y;
				$y = ceil($y / $yearAdj[$aYearType]) * $yearAdj[$aYearType];
			}
			$m = 1;
			$d = 1;
		} elseif ($aMonthType !== false) {
			$monthAdj = [0 => 1, 1 => 6];
			if ($aRound == 0) {
				$m = floor($m / $monthAdj[$aMonthType]) * $monthAdj[$aMonthType];
				$d = 1;
			} else {
				++$m;
				$m = ceil($m / $monthAdj[$aMonthType]) * $monthAdj[$aMonthType];
				$d = 1;
			}
		} elseif ($aDayType !== false) {
			if ($aDayType == 0) {
				if ($aRound == 1) {
					//++$d;
					$h = 23;
					$i = 59;
					$s = 59;
				}
			} else {
				// Adjust to an even week boundary.
				$w = (int) date('w', $aTime); // Day of week 0=Sun, 6=Sat

				// Adjust to start on Mon
				if ($w == 0) {
					$w = 6;
				} else {
					--$w;
				}

				if ($aRound == 0) {
					$d -= $w;
				} else {
					$d += (7 - $w);
					$h = 23;
					$i = 59;
					$s = 59;
				}
			}
		}

		return mktime($h, $i, $s, $m, $d, $y);
	}

	/**
	 * Wrapper for AdjDate that will round a timestamp to an even date rounding
	 * // it downwards.
	 *
	 * @param mixed $aTime
	 * @param mixed $aYearType
	 * @param mixed $aMonthType
	 * @param mixed $aDayType
	 */
	public function AdjStartDate($aTime, $aYearType = false, $aMonthType = false, $aDayType = false){
		return $this->AdjDate($aTime, 0, $aYearType, $aMonthType, $aDayType);
	}

	/**
	 * Wrapper for AdjDate that will round a timestamp to an even date rounding
	 * // it upwards.
	 *
	 * @param mixed $aTime
	 * @param mixed $aYearType
	 * @param mixed $aMonthType
	 * @param mixed $aDayType
	 */
	public function AdjEndDate($aTime, $aYearType = false, $aMonthType = false, $aDayType = false){
		return $this->AdjDate($aTime, 1, $aYearType, $aMonthType, $aDayType);
	}

	/**
	 * Utility Function AdjTime()
	 * // Description: Will round a given time stamp to an even time according to
	 * // argument.
	 *
	 * @param mixed $aTime
	 * @param mixed $aRound
	 * @param mixed $aHourType
	 * @param mixed $aMinType
	 * @param mixed $aSecType
	 */
	public function AdjTime($aTime, $aRound = 0, $aHourType = false, $aMinType = false, $aSecType = false){
		$y = (int) date('Y', $aTime);
		$m = (int) date('m', $aTime);
		$d = (int) date('d', $aTime);
		$h = (int) date('H', $aTime);
		$i = (int) date('i', $aTime);
		$s = (int) date('s', $aTime);
		if ($aHourType !== false) {
			$aHourType %= 6;
			$hourAdj = [0 => 1, 1 => 2, 2 => 3, 3 => 4, 4 => 6, 5 => 12];
			if ($aRound == 0) {
				$h = floor($h / $hourAdj[$aHourType]) * $hourAdj[$aHourType];
			} else {
				if (($h % $hourAdj[$aHourType] == 0) && ($i > 0 || $s > 0)) {
					++$h;
				}
				$h = ceil($h / $hourAdj[$aHourType]) * $hourAdj[$aHourType];
				if ($h >= 24) {
					$aTime += 86400;
					$y = (int) date('Y', $aTime);
					$m = (int) date('m', $aTime);
					$d = (int) date('d', $aTime);
					$h -= 24;
				}
			}
			$i = 0;
			$s = 0;
		} elseif ($aMinType !== false) {
			$aMinType %= 5;
			$minAdj = [0 => 1, 1 => 5, 2 => 10, 3 => 15, 4 => 30];
			if ($aRound == 0) {
				$i = floor($i / $minAdj[$aMinType]) * $minAdj[$aMinType];
			} else {
				if (($i % $minAdj[$aMinType] == 0) && $s > 0) {
					++$i;
				}
				$i = ceil($i / $minAdj[$aMinType]) * $minAdj[$aMinType];
				if ($i >= 60) {
					$aTime += 3600;
					$y = (int) date('Y', $aTime);
					$m = (int) date('m', $aTime);
					$d = (int) date('d', $aTime);
					$h = (int) date('H', $aTime);
					$i = 0;
				}
			}
			$s = 0;
		} elseif ($aSecType !== false) {
			$aSecType %= 5;
			$secAdj = [0 => 1, 1 => 5, 2 => 10, 3 => 15, 4 => 30];
			if ($aRound == 0) {
				$s = floor($s / $secAdj[$aSecType]) * $secAdj[$aSecType];
			} else {
				$s = ceil($s / $secAdj[$aSecType] * 1.0) * $secAdj[$aSecType];
				if ($s >= 60) {
					$s = 0;
					$aTime += 60;
					$y = (int) date('Y', $aTime);
					$m = (int) date('m', $aTime);
					$d = (int) date('d', $aTime);
					$h = (int) date('H', $aTime);
					$i = (int) date('i', $aTime);
				}
			}
		}

		return mktime($h, $i, $s, $m, $d, $y);
	}

	/**
	 * Wrapper for AdjTime that will round a timestamp to an even time rounding
	 * // it downwards.
	 * // Example: AdjStartTime(mktime(18,27,13,2,22,2005),false,2) => 18:20.
	 *
	 * @param mixed $aTime
	 * @param mixed $aHourType
	 * @param mixed $aMinType
	 * @param mixed $aSecType
	 */
	public function AdjStartTime($aTime, $aHourType = false, $aMinType = false, $aSecType = false){
		return $this->AdjTime($aTime, 0, $aHourType, $aMinType, $aSecType);
	}

	/**
	 * Wrapper for AdjTime that will round a timestamp to an even time rounding
	 * // it upwards
	 * // Example: AdjEndTime(mktime(18,27,13,2,22,2005),false,2) => 18:30.
	 *
	 * @param mixed $aTime
	 * @param mixed $aHourType
	 * @param mixed $aMinType
	 * @param mixed $aSecType
	 */
	public function AdjEndTime($aTime, $aHourType = false, $aMinType = false, $aSecType = false){
		return $this->AdjTime($aTime, 1, $aHourType, $aMinType, $aSecType);
	}

	/**
	 * DateAutoScale
	 * // Autoscale a date axis given start and end time
	 * // Returns an array ($start,$end,$major,$minor,$format).
	 *
	 * @param mixed $aStartTime
	 * @param mixed $aEndTime
	 * @param mixed $aDensity
	 * @param mixed $aAdjust
	 */
	public function DoDateAutoScale($aStartTime, $aEndTime, $aDensity = 0, $aAdjust = true){
		// Format of array
		// array ( Decision point,  array( array( Major-scale-step-array ),
		//	   array( Minor-scale-step-array ),
		//	   array( 0=date-adjust, 1=time-adjust, adjustment-alignment) )
		//
		$scalePoints =
			[
			/* Intervall larger than 10 years */
			SECPERYEAR * 10, [[SECPERYEAR * 5, SECPERYEAR * 2],
				[SECPERYEAR],
				[0, YEARADJ_1, 0, YEARADJ_1], ],

			/* Intervall larger than 2 years */
			SECPERYEAR * 2, [[SECPERYEAR], [SECPERYEAR],
				[0, YEARADJ_1], ],

			/* Intervall larger than 90 days (approx 3 month) */
			SECPERDAY * 90, [[SECPERDAY * 30, SECPERDAY * 14, SECPERDAY * 7, SECPERDAY],
				[SECPERDAY * 5, SECPERDAY * 7, SECPERDAY, SECPERDAY],
				[0, MONTHADJ_1, 0, DAYADJ_WEEK, 0, DAYADJ_1, 0, DAYADJ_1], ],

			/* Intervall larger than 30 days (approx 1 month) */
			SECPERDAY * 30, [[SECPERDAY * 14, SECPERDAY * 7, SECPERDAY * 2, SECPERDAY],
				[SECPERDAY, SECPERDAY, SECPERDAY, SECPERDAY],
				[0, DAYADJ_WEEK, 0, DAYADJ_1, 0, DAYADJ_1, 0, DAYADJ_1], ],

			/* Intervall larger than 7 days */
			SECPERDAY * 7, [[SECPERDAY, SECPERHOUR * 12, SECPERHOUR * 6, SECPERHOUR * 2],
				[SECPERHOUR * 6, SECPERHOUR * 3, SECPERHOUR, SECPERHOUR],
				[0, DAYADJ_1, 1, HOURADJ_12, 1, HOURADJ_6, 1, HOURADJ_1], ],

			/* Intervall larger than 1 day */
			SECPERDAY, [[SECPERDAY, SECPERHOUR * 12, SECPERHOUR * 6, SECPERHOUR * 2, SECPERHOUR],
				[SECPERHOUR * 6, SECPERHOUR * 2, SECPERHOUR, SECPERHOUR, SECPERHOUR],
				[1, HOURADJ_12, 1, HOURADJ_6, 1, HOURADJ_1, 1, HOURADJ_1], ],

			/* Intervall larger than 12 hours */
			SECPERHOUR * 12, [[SECPERHOUR * 2, SECPERHOUR, SECPERMIN * 30, 900, 600],
				[1800, 1800, 900, 300, 300],
				[1, HOURADJ_1, 1, MINADJ_30, 1, MINADJ_15, 1, MINADJ_10, 1, MINADJ_5], ],

			/* Intervall larger than 2 hours */
			SECPERHOUR * 2, [[SECPERHOUR, SECPERMIN * 30, 900, 600, 300],
				[1800, 900, 300, 120, 60],
				[1, HOURADJ_1, 1, MINADJ_30, 1, MINADJ_15, 1, MINADJ_10, 1, MINADJ_5], ],

			/* Intervall larger than 1 hours */
			SECPERHOUR, [[SECPERMIN * 30, 900, 600, 300], [900, 300, 120, 60],
				[1, MINADJ_30, 1, MINADJ_15, 1, MINADJ_10, 1, MINADJ_5], ],

			/* Intervall larger than 30 min */
			SECPERMIN * 30, [[SECPERMIN * 15, SECPERMIN * 10, SECPERMIN * 5, SECPERMIN],
				[300, 300, 60, 10],
				[1, MINADJ_15, 1, MINADJ_10, 1, MINADJ_5, 1, MINADJ_1], ],

			/* Intervall larger than 1 min */
			SECPERMIN, [[SECPERMIN, 15, 10, 5],
				[15, 5, 2, 1],
				[1, MINADJ_1, 1, SECADJ_15, 1, SECADJ_10, 1, SECADJ_5], ],

			/* Intervall larger than 10 sec */
			10, [[5, 2],
				[1, 1],
				[1, SECADJ_5, 1, SECADJ_1], ],

			/* Intervall larger than 1 sec */
			1, [[1],
				[1],
				[1, SECADJ_1], ],
		];

		$ns = safe_count($scalePoints);
		// Establish major and minor scale units for the date scale
		$diff = $aEndTime - $aStartTime;
		if ($diff < 1) {
			return false;
		}

		$done = false;
		$i	= 0;
		while (!$done) {
			if ($diff > $scalePoints[2 * $i]) {
				// Get major and minor scale for this intervall
				$scaleSteps = $scalePoints[2 * $i + 1];
				$major	  = $scaleSteps[0][min($aDensity, safe_count($scaleSteps[0]) - 1)];
				// Try to find out which minor step looks best
				$minor = $scaleSteps[1][min($aDensity, safe_count($scaleSteps[1]) - 1)];
				if ($aAdjust) {
					// Find out how we should align the start and end timestamps
					$idx = 2 * min($aDensity, floor(safe_count($scaleSteps[2]) / 2) - 1);
					if ($scaleSteps[2][$idx] === 0) {
						// Use date adjustment
						$adj = $scaleSteps[2][$idx + 1];
						if ($adj >= 30) {
							$start = $this->AdjStartDate($aStartTime, $adj - 30);
							$end   = $this->AdjEndDate($aEndTime, $adj - 30);
						} elseif ($adj >= 20) {
							$start = $this->AdjStartDate($aStartTime, false, $adj - 20);
							$end   = $this->AdjEndDate($aEndTime, false, $adj - 20);
						} else {
							$start = $this->AdjStartDate($aStartTime, false, false, $adj);
							$end   = $this->AdjEndDate($aEndTime, false, false, $adj);
							// We add 1 second for date adjustment to make sure we end on 00:00 the following day
							// This makes the final major tick be srawn when we step day-by-day instead of ending
							// on xx:59:59 which would not draw the final major tick
							++$end;
						}
					} else {
						// Use time adjustment
						$adj = $scaleSteps[2][$idx + 1];
						if ($adj >= 30) {
							$start = $this->AdjStartTime($aStartTime, $adj - 30);
							$end   = $this->AdjEndTime($aEndTime, $adj - 30);
						} elseif ($adj >= 20) {
							$start = $this->AdjStartTime($aStartTime, false, $adj - 20);
							$end   = $this->AdjEndTime($aEndTime, false, $adj - 20);
						} else {
							$start = $this->AdjStartTime($aStartTime, false, false, $adj);
							$end   = $this->AdjEndTime($aEndTime, false, false, $adj);
						}
					}
				}
				// If the overall date span is larger than 1 day ten we show date
				$format = '';
				if (($end - $start) > SECPERDAY) {
					$format = 'Y-m-d ';
				}
				// If the major step is less than 1 day we need to whow hours + min
				if ($major < SECPERDAY) {
					$format .= 'H:i';
				}
				// If the major step is less than 1 min we need to show sec
				if ($major < 60) {
					$format .= ':s';
				}
				$done = true;
			}
			++$i;
		}

		return [$start, $end, $major, $minor, $format];
	}

	// Overrides the automatic determined date format. Must be a valid date() format string
	public function SetDateFormat($aFormat){
		$this->date_format = $aFormat;
		$this->ticks->SetLabelDateFormat($this->date_format);
	}

	public function AdjustForDST($aFlg = true){
		$this->ticks->AdjustForDST($aFlg);
	}

	public function SetDateAlign($aStartAlign, $aEndAlign = false){
		if ($aEndAlign === false) {
			$aEndAlign = $aStartAlign;
		}
		$this->iStartAlign = $aStartAlign;
		$this->iEndAlign   = $aEndAlign;
	}

	public function SetTimeAlign($aStartAlign, $aEndAlign = false){
		if ($aEndAlign === false) {
			$aEndAlign = $aStartAlign;
		}
		$this->iStartTimeAlign = $aStartAlign;
		$this->iEndTimeAlign   = $aEndAlign;
	}

	public function AutoScale($img, $aStartTime, $aEndTime, $aNumSteps, $_adummy = false){
		// We need to have one dummy argument to make the signature of AutoScale()
		// identical to LinearScale::AutoScale
		if ($aStartTime == $aEndTime) {
			// Special case when we only have one data point.
			// Create a small artifical intervall to do the autoscaling
			$aStartTime -= 10;
			$aEndTime += 10;
		}
		$done = false;
		$i	= 0;
		while (!$done && $i < 5) {
			list($adjstart, $adjend, $maj, $min, $format) = $this->DoDateAutoScale($aStartTime, $aEndTime, $i);
			$n											= floor(($adjend - $adjstart) / $maj);
			if ($n * 1.7 > $aNumSteps) {
				$done = true;
			}
			++$i;
		}

		/*
		if( 0 ) { // DEBUG
		echo "	Start =".date("Y-m-d H:i:s",$aStartTime)."<br>";
		echo "	End   =".date("Y-m-d H:i:s",$aEndTime)."<br>";
		echo "Adj Start =".date("Y-m-d H:i:s",$adjstart)."<br>";
		echo "Adj End   =".date("Y-m-d H:i:s",$adjend)."<p>";
		echo "Major = $maj s, ".floor($maj/60)."min, ".floor($maj/3600)."h, ".floor($maj/86400)."day<br>";
		echo "Min = $min s, ".floor($min/60)."min, ".floor($min/3600)."h, ".floor($min/86400)."day<br>";
		echo "Format=$format<p>";
		}
		 */

		if ($this->iStartTimeAlign !== false && $this->iStartAlign !== false) {
			Util\JpGraphError::RaiseL(3001);
			//('It is only possible to use either SetDateAlign() or SetTimeAlign() but not both');
		}

		if ($this->iStartTimeAlign !== false) {
			if ($this->iStartTimeAlign >= 30) {
				$adjstart = $this->AdjStartTime($aStartTime, $this->iStartTimeAlign - 30);
			} elseif ($this->iStartTimeAlign >= 20) {
				$adjstart = $this->AdjStartTime($aStartTime, false, $this->iStartTimeAlign - 20);
			} else {
				$adjstart = $this->AdjStartTime($aStartTime, false, false, $this->iStartTimeAlign);
			}
		}
		if ($this->iEndTimeAlign !== false) {
			if ($this->iEndTimeAlign >= 30) {
				$adjend = $this->AdjEndTime($aEndTime, $this->iEndTimeAlign - 30);
			} elseif ($this->iEndTimeAlign >= 20) {
				$adjend = $this->AdjEndTime($aEndTime, false, $this->iEndTimeAlign - 20);
			} else {
				$adjend = $this->AdjEndTime($aEndTime, false, false, $this->iEndTimeAlign);
			}
		}

		if ($this->iStartAlign !== false) {
			if ($this->iStartAlign >= 30) {
				$adjstart = $this->AdjStartDate($aStartTime, $this->iStartAlign - 30);
			} elseif ($this->iStartAlign >= 20) {
				$adjstart = $this->AdjStartDate($aStartTime, false, $this->iStartAlign - 20);
			} else {
				$adjstart = $this->AdjStartDate($aStartTime, false, false, $this->iStartAlign);
			}
		}
		if ($this->iEndAlign !== false) {
			if ($this->iEndAlign >= 30) {
				$adjend = $this->AdjEndDate($aEndTime, $this->iEndAlign - 30);
			} elseif ($this->iEndAlign >= 20) {
				$adjend = $this->AdjEndDate($aEndTime, false, $this->iEndAlign - 20);
			} else {
				$adjend = $this->AdjEndDate($aEndTime, false, false, $this->iEndAlign);
			}
		}
		$this->Update($img, $adjstart, $adjend);
		if (!$this->ticks->IsSpecified()) {
			$this->ticks->Set($maj, $min);
		}

		if ($this->date_format == '') {
			$this->ticks->SetLabelDateFormat($format);
		} else {
			$this->ticks->SetLabelDateFormat($this->date_format);
		}
	}
}
